import { useEffect, useState } from 'react';
import Taro from '@tarojs/taro';
import { Observable, timer, Subject, OperatorFunction, merge, EMPTY, from, concat, Subscription } from "rxjs";
import { mergeMap, finalize, takeUntil, map, retryWhen, filter, first, isEmpty, throwIfEmpty, catchError, connect, tap } from "rxjs/operators";
import { ApiResponse } from '@/models/ApiResponse';
import { HTTP_STATUS } from '@/constants/status';
import { getAuthorization } from '.';

export function concatIfEmpty<T, D = T>(observable: Observable<D>): OperatorFunction<T, D | T> {
  return source$ =>
    source$.pipe(
      connect(sharedSource =>
        merge(
          sharedSource,
          sharedSource.pipe(
            isEmpty(),
            mergeMap(empty => (empty ? observable : EMPTY))
          )
        )
      )
    )
}

export const switchIfEmpty = <I, O>(to: Observable<O>) => (o: Observable<I>) => o.pipe(
  throwIfEmpty(),
  catchError(() => to),
)

export const definedOrEmpty = <T>(o: Observable<T>) => o.pipe(
  first(),
  filter(value => value !== undefined),
)

// retryWhen操作符专用重试策略
export const genericRetryStrategy = ({
  maxRetryAttempts = 2, // 最大重试次数
  scalingDuration = 1000, // 重试时间
  excludedStatusCodes = [] // 不需要重试的http状态码
}: {
  maxRetryAttempts?: number,
  scalingDuration?: number,
  excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
  return attempts.pipe(
    mergeMap((error, i) => {
      const retryAttempt: number = i + 1;
      // if maximum number of retries have been met
      // or response is a status code we don't wish to retry, throw error
      if (retryAttempt > maxRetryAttempts || excludedStatusCodes.find(e => e === error.status)) {
        throw new Error(error.message);
      }
      console.log(`重试次数 ${retryAttempt}: 重试时间 ${retryAttempt * scalingDuration}ms`);
      // retry after 1s, 2s, etc...
      return timer(retryAttempt * scalingDuration);
    }),
    finalize(() => console.log("重试完成!"))
  )
}

export const untilDestroyed = (componentInstance: any, destroyMethodName = 'componentWillUnmount',) => <T>(source: Observable<T>) => {
  const originalDestroy = componentInstance[destroyMethodName];

  if (!componentInstance['__takeUntilDestroy']) {
    componentInstance['__takeUntilDestroy'] = new Subject();

    componentInstance[destroyMethodName] = function (...args: any) {
      componentInstance['__takeUntilDestroy'].next(true);
      componentInstance['__takeUntilDestroy'].complete();
      typeof originalDestroy === 'function' && originalDestroy.apply(this, args);
    }
  }
  return source.pipe(takeUntil<T>(componentInstance['__takeUntilDestroy']));
}

export function delayUntil<T>(notifier: Observable<unknown>): OperatorFunction<T, T> {
  return source =>
    source.pipe(
      connect(published => {
        const delayed = new Observable<T>(subscriber => {
          let buffering = true;
          const buffer: T[] = [];
          const subscription = new Subscription();
          subscription.add(
            notifier.subscribe(
              {
                next: () => {
                  buffer.forEach(value => subscriber.next(value));
                  subscriber.complete();
                },
                error: error => subscriber.error(error),
                complete: () => {
                  buffering = false;
                  buffer.length = 0;
                }
              }
            )
          );
          subscription.add(
            published.subscribe(
              value => buffering && buffer.push(value),
              error => subscriber.error(error)
            )
          );
          subscription.add(() => {
            buffer.length = 0;
          });
          return subscription;
        });
        return concat(delayed, published);
      })
    )
}

//判断登录状态的操作符，等待登录完成再进行后续操作
export function untilTrue<T>(): OperatorFunction<T, T> {
  return source$ =>
    source$.pipe(
      map(source => {
        if (!source) {
          // 错误将由 retryWhen 接收
          throw source;
        }
        return source;
      }),
      //FIXME上面的map操作符和下面的retryWhen操作符主要作用是等待登录完成再调用获取个人信息接口
      retryWhen(genericRetryStrategy({
        maxRetryAttempts: 2, // 最大重试次数
        scalingDuration: 300, // 重试时间
      }))
    )
}

//用于React中的useEffect自动清理的函数
export function onEmit<T>(source$: Observable<T>, nextFn: (value: T) => void, errFn?: (err: any) => void): Subscription {
  return source$.subscribe({ next: nextFn, error: errFn });
}

export type ResetStreamSource<T> = (source$: Observable<T>) => void;
export function useObservable<T>(observable$: Observable<T>): [T | undefined, ResetStreamSource<T>];
export function useObservable<T>(observable$: Observable<T>, initialValue: T | (() => T)): [T, ResetStreamSource<T>];
/**
 * Manage Observable subscµription and report value emission.
 * - support dynamic reset of stream source
 * - support auto-unsubscribe when view component unmounts
 * @param observable$ Stream source
 * @param initialValue initial value; to simulate 1st stream emission
 */
export function useObservable<T>(
  observable$: Observable<T>,
  initialValue?: T | (() => T)
): [T | undefined, ResetStreamSource<T>] {
  const [source$, setObservable] = useState<Observable<T>>(observable$);
  const [value, setValue] = useState<T | undefined>(initialValue);
  const reportError = (err: any) => {
    setValue(undefined);
    console.error(`useObservable() error: ${JSON.stringify(err)}`);
  };

  useEffect(() => {
    if (source$) {
      const s = source$.subscribe(setValue, reportError);
      return () => {
        s.unsubscribe();
      };
    }
  }, [source$]);

  return [value, setObservable];
}

interface RxConfig extends Taro.request.Option {
  statusCode?: number;
  withJwt?: boolean;   //默认不带jwt
}

// 封装自定义网络请求，如果授权失败则跳转到登录页面，成功则直接返回数据，其他失败情况则抛异常处理
export function rxRequest<T>(params: RxConfig) {
  //如果需要授权
  if (params.withJwt) {
    const newHeaders = Object.assign(params.header || {}, getAuthorization() || {});
    params.header = newHeaders;
    //移除多余字段
    delete params.withJwt;
  }

  return from(Taro.request(params))
    .pipe(
      // 默认重试2次，间隔3秒
      retryWhen(genericRetryStrategy()),
      map(res => {
        if (res.statusCode === HTTP_STATUS.SUCCESS) {
          return res.data.data as T;
        } else {
          let errMsg = '';

          if (res.statusCode === HTTP_STATUS.UNAUTHORIZED) {
            errMsg = '未登录';
          } else if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
            errMsg = '请求资源不存在';
          } else if (res.statusCode === HTTP_STATUS.BAD_GATEWAY) {
            errMsg = '服务端出现了问题';
          } else if (res.statusCode === HTTP_STATUS.FORBIDDEN) {
            errMsg = '没有权限访问';
          } else if (res.statusCode === HTTP_STATUS.SERVER_ERROR) {
            const rsp = res.data;
            errMsg = rsp.msg;
          }

          throw new Error(errMsg);
        }
      })
    )
}